/*
 * FreeRTOS+FAT V2.3.3
 * Copyright (C) 2021 Amazon.com, Inc. or its affiliates. All Rights Reserved.
 *
 * Permission is hereby granted, free of charge, to any person obtaining a copy of
 * this software and associated documentation files (the "Software"), to deal in
 * the Software without restriction, including without limitation the rights to
 * use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
 * the Software, and to permit persons to whom the Software is furnished to do so,
 * subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in all
 * copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
 * FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
 * COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
 * IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
 * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
 *
 * https://www.FreeRTOS.org
 * https://github.com/FreeRTOS
 *
 */

/* FreeRTOS includes. */
#include "FreeRTOS.h"
#include "task.h"
#include "portable.h"

/* FreeRTOS+FAT includes. */
#include "ff_headers.h"
#include "ff_stdio.h"

#if ( ffconfigTIME_SUPPORT != 0 )
    #include <time.h>
#endif


#ifndef SIZE_MAX
    #define SIZE_MAX    ( ( size_t ) -1 )
#endif

/* The number of bytes to write at a time when extending the length of a file
 * in a call to ff_truncate(). */
#define stdioTRUNCATE_WRITE_LENGTH    512

/* Bits set to indicate whether ".." should be included as well as ".". */
#define stdioDIR_ENTRY_DOT_1          ( 1U & 0x03U )
#define stdioDIR_ENTRY_DOT_2          ( 2U & 0x03U )

/* The directory entries '.' and '..' will show a file size of 1 KB. */
#define stdioDOT_ENTRY_FILE_SIZE      1024

/*-----------------------------------------------------------*/

/*
 * Add the CWD to the beginning of a relative path, and copy the resultant
 * absolute path into a thread local non const buffer.
 */
/*static*/ const char * prvABSPath( const char * pcPath );

/*
 * Translate a +FAT error to a value compatible with errno.h
 * If the value represents an error, it is negative
 * The return value of this function will always be positive
 */
int prvFFErrorToErrno( FF_Error_t xError );

/*
 * Generate a time stamp for the file.
 */
#if ( ffconfigTIME_SUPPORT == 1 )
    static time_t prvFileTime( FF_SystemTime_t * pxTime );
#endif

#if ( ffconfigHAS_CWD == 1 )

/* FreeRTOS+FAT requires two thread local storage pointers.  One for errno
 * and one for the CWD structure. */
    #if ( configNUM_THREAD_LOCAL_STORAGE_POINTERS < 2 )
        #error FreeRTOS+FAT requires two thread local storage pointers so configNUM_THREAD_LOCAL_STORAGE_POINTERS must be at least 2 in FreeRTOSConfig.h
    #endif

/* Each task has its own Current Working Directory (CWD).  The CWD is used
 * to extend relative paths to absolute paths. */
    typedef struct WORKING_DIR
    {
        char pcCWD[ ffconfigMAX_FILENAME - 2 ];  /* The current working directory.  To eliminate warnings in path building
                                                  * code below we make this ffconfigMAX_FILENAME-2. In reality you would have
                                                  * a least one character for filename and one for '/' so this not unreasonable.
                                                  */
        char pcFileName[ ffconfigMAX_FILENAME ]; /* The created absolute path. */
    } WorkingDirectory_t;

/*
 * Lookup the CWD of the current task.
 */
    static WorkingDirectory_t * pxFindCWD( void );

/*
 * Convert a string which may contain a relative path into a string that
 * will only contain an absolute path.
 */
    static const char * prvProcessRelativePaths( const char * pcPath );

#else /* ffconfigHAS_CWD */

/* FreeRTOS+FAT requires one thread local storage pointers for errno. */
    #if ( configNUM_THREAD_LOCAL_STORAGE_POINTERS < 2 )
        #error FreeRTOS+FAT requires one thread local storage pointers so configNUM_THREAD_LOCAL_STORAGE_POINTERS must be at least 1 in FreeRTOSConfig.h
    #endif

/* Only absolute paths are supported so define away the prvABSPath()
 * function. */
/*static*/ const char * prvABSPath( const char * pcPath )
    {
        return pcPath;
    }

#endif /* ffconfigHAS_CWD */


#if ( ffconfigUSE_DELTREE != 0 )

/*
 * Remove all files and directories starting from a certain path.
 * This function uses recursion - which breaches the coding standard.  USE
 * WITH CARE.
 */
    static int ff_deltree_recurse( char * pcPath );
#endif

/*-----------------------------------------------------------*/

FF_FILE * ff_fopen( const char * pcFile,
                    const char * pcMode )
{
    FF_FILE * pxStream = NULL;
    FF_DirHandler_t xHandler;
    FF_Error_t xError;
    uint8_t ucMode;

    /* Insert the current working directory in front of relative paths. */
    pcFile = prvABSPath( pcFile );

    /* Look-up the I/O manager for the file system. */
    if( FF_FS_Find( pcFile, &xHandler ) == pdFALSE )
    {
        stdioSET_ERRNO( pdFREERTOS_ERRNO_ENXIO ); /* No such device or address. */
    }
    else
    {
        /* Now 'xHandler.pcPath' contains an absolute path within the file system.
         * Translate a type string "r|w|a[+]" to +FAT's mode bits. */
        ucMode = FF_GetModeBits( pcMode );

        pxStream = FF_Open( xHandler.pxManager, xHandler.pcPath, ucMode, &xError );
        stdioSET_ERRNO( prvFFErrorToErrno( xError ) );

        #if ( ffconfigUSE_NOTIFY != 0 )
        {
            if( ( pxStream != NULL ) && ( ( ucMode & ( FF_MODE_WRITE | FF_MODE_APPEND ) ) != 0 ) )
            {
                /*_RB_ Function	name needs updating. */
                callFileEvents( pcFile, eFileCreate );
            }
        }
        #endif /* ffconfigUSE_NOTIFY */

        #if ( ffconfigDEV_SUPPORT != 0 )
        {
            if( pxStream != NULL )
            {
                FF_Device_Open( pcFile, pxStream );
            }
        }
        #endif /* ffconfigDEV_SUPPORT */
    }

    return pxStream;
}
/*-----------------------------------------------------------*/

int ff_fclose( FF_FILE * pxStream )
{
    FF_Error_t xError;
    int iReturn, ff_errno;

    #if ( ffconfigDEV_SUPPORT != 0 )
    {
        /* Currently device support is in an experimental state.  It will allow
         * to create virtual files. The I/O data to those files will be redirected
         * to their connected "drivers". */
        if( pxStream != NULL )
        {
            FF_Device_Close( pxStream );
        }
    }
    #endif

    xError = FF_Close( pxStream );
    ff_errno = prvFFErrorToErrno( xError );

    if( ff_errno == 0 )
    {
        iReturn = 0;
    }
    else
    {
        /* Return -1 for error as per normal fclose() semantics. */
        iReturn = -1;
    }

    /* Store the errno to thread local storage. */
    stdioSET_ERRNO( ff_errno );

    return iReturn;
}
/*-----------------------------------------------------------*/

int ff_fseek( FF_FILE * pxStream,
              long lOffset,
              int iWhence )
{
    FF_Error_t xError;
    int iReturn, ff_errno;

    #if ( ffconfigDEV_SUPPORT != 0 )
        if( pxStream->pxDevNode != NULL )
        {
            xError = FF_Device_Seek( pxStream, ( int32_t ) lOffset, iWhence );
        }
        else
    #endif
    {
        xError = FF_Seek( pxStream, ( int32_t ) lOffset, iWhence );
    }

    ff_errno = prvFFErrorToErrno( xError );

    if( ff_errno == 0 )
    {
        iReturn = 0;
    }
    else
    {
        if( xError == FF_ERR_FILE_SEEK_INVALID_POSITION )
        {
            /* Illegal position, outside the file's space */
            ff_errno = pdFREERTOS_ERRNO_ESPIPE;
        }
        else if( xError == FF_ERR_FILE_SEEK_INVALID_ORIGIN )
        {
            /* Illegal parameter value for iWhence: SET,CUR,END. */
            ff_errno = pdFREERTOS_ERRNO_EINVAL;
        }

        /* Return -1 for error as per normal fseek() semantics. */
        iReturn = -1;
    }

    /* Store the errno to thread local storage. */
    stdioSET_ERRNO( ff_errno );

    return iReturn;
}
/*-----------------------------------------------------------*/

void ff_rewind( FF_FILE * pxStream )
{
    ff_fseek( pxStream, 0, FF_SEEK_SET );

    /* Rewind is supposed to reset errno unconditionally.  Store the errno to
     * thread local storage. */
    stdioSET_ERRNO( 0 );
}
/*-----------------------------------------------------------*/

long ff_ftell( FF_FILE * pxStream )
{
    long lResult;

    if( pxStream == NULL )
    {
        /* Store the errno to thread local storage. */
        stdioSET_ERRNO( pdFREERTOS_ERRNO_EBADF );

        /* Return -1 for error as per normal ftell() semantics. */
        lResult = -1;
    }
    else
    {
        lResult = ( long ) pxStream->ulFilePointer;
    }

    return lResult;
}
/*-----------------------------------------------------------*/

int ff_feof( FF_FILE * pxStream )
{
    int iResult;
    FF_Error_t xError;

    xError = FF_CheckValid( pxStream );

    if( FF_isERR( xError ) == pdFALSE )
    {
        /* Store the errno to thread local storage. */
        stdioSET_ERRNO( 0 );

        if( pxStream->ulFilePointer >= pxStream->ulFileSize )
        {
            iResult = pdTRUE;
        }
        else
        {
            iResult = pdFALSE;
        }
    }
    else
    {
        /* Store the errno to thread local storage. */
        stdioSET_ERRNO( prvFFErrorToErrno( xError ) );

        /* The file was invalid so a non-zero value cannot be returned. */
        iResult = pdFALSE;
    }

    return iResult;
}
/*-----------------------------------------------------------*/

size_t ff_fread( void * pvBuffer,
                 size_t xSize,
                 size_t xItems,
                 FF_FILE * pxStream )
{
    int32_t iReturned;
    size_t xReturn;
    int ff_errno;

    #if ( ffconfigDEV_SUPPORT != 0 )
        if( pxStream->pxDevNode != NULL )
        {
            iReturned = FF_Device_Read( pvBuffer, xSize, xItems, pxStream );
        }
        else
    #endif
    {
        iReturned = FF_Read( pxStream, ( uint32_t ) xSize, ( uint32_t ) xItems, ( uint8_t * ) pvBuffer );
    }

    ff_errno = prvFFErrorToErrno( iReturned );

    if( ff_errno == pdFREERTOS_ERRNO_NONE )
    {
        /* As per the standard fread() semantics, the return value is the number
         * of complete items read, which will only equal the number of bytes
         * transferred when the item size is 1. */
        xReturn = ( size_t ) iReturned;
    }
    else
    {
        xReturn = 0;
    }

    /* Store the errno to thread local storage. */
    stdioSET_ERRNO( ff_errno );

    return xReturn;
}
/*-----------------------------------------------------------*/

size_t ff_fwrite( const void * pvBuffer,
                  size_t xSize,
                  size_t xItems,
                  FF_FILE * pxStream )
{
    int32_t iReturned;
    size_t xReturn;
    int ff_errno;

    #if ( ffconfigDEV_SUPPORT != 0 )
        if( pxStream->pxDevNode != NULL )
        {
            iReturned = FF_Device_Write( pvBuffer, xSize, xItems, pxStream );
        }
        else
    #endif
    {
        iReturned = FF_Write( pxStream, ( uint32_t ) xSize, ( uint32_t ) xItems, ( uint8_t * ) pvBuffer );
    }

    ff_errno = prvFFErrorToErrno( iReturned );

    if( ff_errno == pdFREERTOS_ERRNO_NONE )
    {
        /* As per the standard fwrite() semantics, the return value is the
         * number of complete items read, which will only equal the number of bytes
         * transferred when the item size is 1. */
        xReturn = ( size_t ) iReturned;
    }
    else
    {
        xReturn = 0;
    }

    /* Store the errno to thread local storage. */
    stdioSET_ERRNO( ff_errno );

    return xReturn;
}
/*-----------------------------------------------------------*/

int ff_fgetc( FF_FILE * pxStream )
{
    int32_t iResult;
    int ff_errno;

    iResult = FF_GetC( pxStream );
    ff_errno = prvFFErrorToErrno( iResult );

    if( ff_errno != 0 )
    {
        iResult = FF_EOF;
    }

    /* Store the errno to thread local storage. */
    stdioSET_ERRNO( ff_errno );

    return iResult;
}
/*-----------------------------------------------------------*/

int ff_fputc( int iChar,
              FF_FILE * pxStream )
{
    int iResult, ff_errno;

    iResult = FF_PutC( pxStream, ( uint8_t ) iChar );
    ff_errno = prvFFErrorToErrno( iResult );

    if( ff_errno != 0 )
    {
        iResult = FF_EOF;
    }

    /* Store the errno to thread local storage. */
    stdioSET_ERRNO( ff_errno );

    return iResult;
}
/*-----------------------------------------------------------*/

#if ( ffconfigFPRINTF_SUPPORT == 1 )

    int ff_fprintf( FF_FILE * pxStream,
                    const char * pcFormat,
                    ... )
    {
        int iCount;
        size_t xResult;
        char * pcBuffer;
        va_list xArgs;

        pcBuffer = ( char * ) ffconfigMALLOC( ffconfigFPRINTF_BUFFER_LENGTH );

        if( pcBuffer == NULL )
        {
            /* Store the errno to thread local storage. */
            stdioSET_ERRNO( pdFREERTOS_ERRNO_ENOMEM );
            iCount = -1;
        }
        else
        {
            va_start( xArgs, pcFormat );
            iCount = vsnprintf( pcBuffer, ffconfigFPRINTF_BUFFER_LENGTH, pcFormat, xArgs );
            va_end( xArgs );

            /* ff_fwrite() will set ff_errno. */
            if( iCount > 0 )
            {
                xResult = ff_fwrite( pcBuffer, ( size_t ) 1, ( size_t ) iCount, pxStream );

                if( xResult < ( size_t ) iCount )
                {
                    iCount = -1;
                }
            }

            ffconfigFREE( pcBuffer );
        }

        return iCount;
    }

#endif /* if ( ffconfigFPRINTF_SUPPORT == 1 ) */
/*-----------------------------------------------------------*/

/*_RB_ to comply with the norm, the second parameter should be an int, but size_t
 * is more appropriate. */
char * ff_fgets( char * pcBuffer,
                 size_t xCount,
                 FF_FILE * pxStream )
{
    int32_t xResult;
    int ff_errno;

    xResult = FF_GetLine( pxStream, ( char * ) pcBuffer, ( uint32_t ) xCount );

    /* This call seems to result in errno being incorrectly set to
     * FF_ERR_IOMAN_NO_MOUNTABLE_PARTITION when an EOF is encountered. */
    ff_errno = prvFFErrorToErrno( xResult );

    if( ff_errno != 0 )
    {
        pcBuffer = NULL;
    }

    /* Store the errno to thread local storage. */
    stdioSET_ERRNO( ff_errno );

    return pcBuffer;
}
/*-----------------------------------------------------------*/

int ff_seteof( FF_FILE * pxStream )
{
    FF_Error_t iResult;
    int iReturn, ff_errno;

    iResult = FF_SetEof( pxStream );

    ff_errno = prvFFErrorToErrno( iResult );

    if( ff_errno == 0 )
    {
        iReturn = 0;
    }
    else
    {
        iReturn = FF_EOF;
    }

    /* Store the errno to thread local storage. */
    stdioSET_ERRNO( ff_errno );

    return iReturn;
}
/*-----------------------------------------------------------*/

/*_RB_ The norm would be to return an int, but in either case it is not clear
 * what state the file is left in (open/closed). */
FF_FILE * ff_truncate( const char * pcFileName,
                       long lTruncateSize )
{
    FF_Error_t xResult = 0;
    FF_FILE * pxStream;
    size_t xReturned;
    uint32_t ulLength, ulBytesLeftToAdd, ulBytesToWrite;
    char * pcBufferToWrite;

    pxStream = ff_fopen( pcFileName, "a+" );

    if( pxStream != NULL )
    {
        ulLength = pxStream->ulFileSize;
    }
    else
    {
        ulLength = 0;
    }

    if( pxStream == NULL )
    {
        /* Store the errno to thread local storage. */
        stdioSET_ERRNO( prvFFErrorToErrno( xResult ) );
    }
    else if( ulLength > ( uint32_t ) lTruncateSize )
    {
        /* Seek the desired position */
        xResult = FF_Seek( pxStream, ( int32_t ) lTruncateSize, FF_SEEK_SET );

        /* Make the current position equal to its length */
        if( FF_isERR( xResult ) == pdFALSE )
        {
            xResult = FF_SetEof( pxStream );
        }

        if( FF_isERR( xResult ) != pdFALSE )
        {
            ff_fclose( pxStream );
            pxStream = NULL;
        }

        /* Store the errno to thread local storage. */
        stdioSET_ERRNO( prvFFErrorToErrno( xResult ) );
    }
    else if( ulLength == ( uint32_t ) lTruncateSize )
    {
        /* Nothing to do, the file has the desired size
         * and the open handle will be returned. */
    }
    else
    {
        /* lTruncateSize > ulLength.  The user wants to open this file with a
         * larger size than it currently has.  Fill it with zeros. */
        pcBufferToWrite = ( char * ) ffconfigMALLOC( stdioTRUNCATE_WRITE_LENGTH );

        if( pcBufferToWrite == NULL )
        {
            ff_fclose( pxStream );
            pxStream = NULL;

            /* Store the errno to thread local storage. */
            stdioSET_ERRNO( pdFREERTOS_ERRNO_ENOMEM );
        }
        else
        {
            /* File has to grow */
            ulBytesLeftToAdd = ( ( uint32_t ) lTruncateSize ) - ulLength;

            /* Zeros must be written. */
            memset( pcBufferToWrite, '\0', stdioTRUNCATE_WRITE_LENGTH );

            while( ulBytesLeftToAdd > 0UL )
            {
                if( ( pxStream->ulFileSize % stdioTRUNCATE_WRITE_LENGTH ) != 0 )
                {
                    /* Although +FAT's FF_Write() can handle any size at any
                     * offset, the driver puts data more efficiently if blocks are
                     * written at block boundaries. */
                    ulBytesToWrite = stdioTRUNCATE_WRITE_LENGTH - ( pxStream->ulFileSize % stdioTRUNCATE_WRITE_LENGTH );

                    if( ulBytesToWrite > ulBytesLeftToAdd )
                    {
                        ulBytesToWrite = ulBytesLeftToAdd;
                    }
                }
                else
                {
                    ulBytesToWrite = ulBytesLeftToAdd;

                    if( ulBytesToWrite > stdioTRUNCATE_WRITE_LENGTH )
                    {
                        ulBytesToWrite = stdioTRUNCATE_WRITE_LENGTH;
                    }
                }

                xReturned = ff_fwrite( pcBufferToWrite, sizeof( char ), ulBytesToWrite, pxStream );

                if( xReturned != ( size_t ) ulBytesToWrite )
                {
                    /* Write error.  Close the stream and set the proper .
                     * errno. */
                    ff_fclose( pxStream );
                    pxStream = NULL;

                    /* Not setting ff_errno because it has been set by other
                     * functions from this ff_stdio. */

                    break;
                }

                ulBytesLeftToAdd -= ulBytesToWrite;
            }

            ffconfigFREE( pcBufferToWrite );
        }
    }

    return pxStream;
}
/*-----------------------------------------------------------*/

#if ( ffconfigMKDIR_RECURSIVE == 0 )

/* The normal mkdir() : if assumes that the directories leading to the last
 * element of pcDirectory already exists.  For instance: mkdir( "/a/b/c" ) will
 * succeed if the path "/a/b" already exists. */
    int ff_mkdir( const char * pcDirectory )
    {
        int iResult, ff_errno;
        FF_DirHandler_t xHandler;

        /* In case a CWD is used, get the absolute path. */
        pcDirectory = prvABSPath( pcDirectory );

        /* Find the i/o manager for this path */
        if( FF_FS_Find( pcDirectory, &xHandler ) == pdFALSE )
        {
            /* No such device or address. */
            stdioSET_ERRNO( pdFREERTOS_ERRNO_ENXIO );
            /* Return -1 for error as per normal mkdir() semantics. */
            iResult = -1;
        }
        else
        {
            /* A simple non-recursive make of a directory. */
            iResult = FF_MkDir( xHandler.pxManager, xHandler.pcPath );

            if( FF_GETERROR( iResult ) == FF_ERR_DIR_OBJECT_EXISTS )
            {
                /* No error if the target directory already exists. */
                iResult = FF_ERR_NONE;
            }

            ff_errno = prvFFErrorToErrno( iResult );

            /* Store the errno to thread local storage. */
            stdioSET_ERRNO( ff_errno );

            if( ff_errno == pdFREERTOS_ERRNO_NONE )
            {
                iResult = 0;
            }
            else
            {
                /* Return -1 for error as per normal mkdir() semantics. */
                iResult = -1;
            }
        }

        return iResult;
    }
#else /* ffconfigMKDIR_RECURSIVE */

    #warning This path is not yet included in the regression tests.

/* The 'recursive mkdir() : if the parameter 'xRecursive' is non-zero,
 * the function will try to create the complete path. */
    int ff_mkdir( const char * pcDirectory,
                  int xRecursive )
    {
        int32_t lResult;
        FF_DirHandler_t xHandler;

        /* In case a CWD is used, get the absolute path. */
        pcDirectory = prvABSPath( pcDirectory );

        /* Find the i/o manager for this path */
        if( FF_FS_Find( pcDirectory, &xHandler ) == pdFALSE )
        {
            /* No such device or address.  Store the errno to thread local
             * storage. */
            stdioSET_ERRNO( pdFREERTOS_ERRNO_ENXIO );
            /* Return -1 for error as per normal mkdir() semantics. */
            lResult = -1;
        }
        else
        {
            if( xRecursive == pdFALSE )
            {
                /* A simple non-recursive make of a directory. */
                lResult = FF_MkDir( xHandler.pxManager, xHandler.pcPath );

                if( FF_GETERROR( lResult ) == FF_ERR_DIR_OBJECT_EXISTS )
                {
                    /* No error if the target directory already exists. */
                    lResult = 0;
                }
            }
            else
            {
                /* The recursive option is used. */
                char pcTempPath[ ffconfigMAX_FILENAME ];
                FF_Error_t errCode;
                int iLength = snprintf( pcTempPath, sizeof( pcTempPath ), "%s", xHandler.pcPath );
                char * pcPtr = pcTempPath + 1, * pcPrev;
                const char * pcLast = pcTempPath + iLength;

                lResult = FF_ERR_NONE;

                for( ; ; )
                {
                    for( pcPrev = pcPtr; pcPtr < pcLast; pcPtr++ )
                    {
                        if( *pcPtr == '/' )
                        {
                            *pcPtr = '\0';
                            break;
                        }
                    }

                    if( pcPrev == pcPtr )
                    {
                        break;
                    }

                    errCode = FF_MkDir( xHandler.pxManager, pcTempPath );

                    if( FF_isERR( errCode ) && ( FF_GETERROR( errCode ) != FF_ERR_DIR_OBJECT_EXISTS ) )
                    {
                        lResult = errCode;
                        break;
                    }

                    if( pcPtr >= ( pcLast - 1 ) )
                    {
                        break;
                    }

                    *( pcPtr++ ) = '/';
                }
            }

            /* Store the errno to thread local storage. */
            stdioSET_ERRNO( prvFFErrorToErrno( lResult ) );
        }

        return lResult;
    }
#endif /* ffconfigMKDIR_RECURSIVE */
/*-----------------------------------------------------------*/

int ff_rmdir( const char * pcDirectory )
{
    int32_t lResult;
    int iReturn, ff_errno;
    FF_DirHandler_t xHandler;

    /* In case a CWD is used, get the absolute path */
    pcDirectory = prvABSPath( pcDirectory );

    /* Find the i/o manager which can handle this path. */
    if( FF_FS_Find( pcDirectory, &xHandler ) == pdFALSE )
    {
        ff_errno = pdFREERTOS_ERRNO_ENXIO; /* No such device or address */

        /* Return -1 for error as per normal rmdir() semantics. */
        iReturn = -1;
    }
    else
    {
        lResult = FF_RmDir( xHandler.pxManager, xHandler.pcPath );
        ff_errno = prvFFErrorToErrno( lResult );

        if( ff_errno == 0 )
        {
            iReturn = 0;
        }
        else
        {
            /* Return -1 for error as per normal rmdir() semantics. */
            iReturn = -1;
        }
    }

    /* Store the errno to thread local storage. */
    stdioSET_ERRNO( ff_errno );

    return iReturn;
}
/*-----------------------------------------------------------*/

int ff_remove( const char * pcPath )
{
    FF_DirHandler_t xHandler;
    FF_Error_t xError;
    int iReturn, ff_errno;

    /* In case a CWD is used, get the absolute path */
    pcPath = prvABSPath( pcPath );

    /* Find the i/o manager which can handle this path. */
    if( FF_FS_Find( pcPath, &xHandler ) == pdFALSE )
    {
        /* No such device or address */
        ff_errno = pdFREERTOS_ERRNO_ENXIO;

        /* Return -1 for error as per normal remove() semantics. */
        iReturn = -1;
    }
    else
    {
        xError = FF_RmFile( xHandler.pxManager, xHandler.pcPath );
        ff_errno = prvFFErrorToErrno( xError );

        #if ffconfigUSE_NOTIFY
        {
            if( FF_isERR( xError ) == pdFALSE )
            {
                callFileEvents( pcPath, eFileRemove );
            }
        }
        #endif

        if( ff_errno == 0 )
        {
            iReturn = 0;
        }
        else
        {
            /* Return -1 for error as per normal remove() semantics. */
            iReturn = -1;
        }
    }

    /* Store the errno to thread local storage. */
    stdioSET_ERRNO( ff_errno );
    return iReturn;
}
/*-----------------------------------------------------------*/
/*_RB_ Last parameter not documented. */
int ff_rename( const char * pcOldName,
               const char * pcNewName,
               int bDeleteIfExists )
{
    FF_DirHandler_t xHandlers[ 2 ];
    FF_Error_t xError = FF_ERR_NONE;
    int ff_errno = 0, iReturn;

    #if ( ffconfigHAS_CWD != 0 )
        char * pcOldCopy;
        size_t xSize;
    #endif

    /* In case a CWD is used, get the absolute path */
    pcOldName = prvABSPath( pcOldName );

    /* Find the i/o manager which can handle this path */
    if( FF_FS_Find( pcOldName, &xHandlers[ 0 ] ) == pdFALSE )
    {
        xError = FF_createERR( FF_ERR_NULL_POINTER, FF_MOVE );
        ff_errno = pdFREERTOS_ERRNO_ENXIO; /* No such device or address */
    }
    else
    {
        #if ( ffconfigHAS_CWD != 0 )
        {
            xSize = strlen( xHandlers[ 0 ].pcPath ) + 1;
            pcOldCopy = ( char * ) ffconfigMALLOC( xSize );

            if( pcOldCopy == NULL )
            {
                /* Could not allocate space to store a file name. */
                ff_errno = pdFREERTOS_ERRNO_ENOMEM;
                xError = FF_createERR( FF_ERR_NOT_ENOUGH_MEMORY, FF_MOVE );
            }
            else
            {
                /* The function prvABSPath() returns a pointer to the task
                 * storage space. Rename needs to call it twice and therefore the
                 * path must be stored before it gets overwritten. */
                memcpy( pcOldCopy, xHandlers[ 0 ].pcPath, xSize );
                xHandlers[ 0 ].pcPath = pcOldCopy;
            }
        }
        #endif /* ffconfigHAS_CWD != 0 */

        #if ( ffconfigHAS_CWD != 0 )
            if( pcOldCopy != NULL )
        #endif /* ffconfigHAS_CWD != 0 */
        {
            pcNewName = prvABSPath( pcNewName );

            /* Find the i/o manager which can handle this path */
            if( FF_FS_Find( pcNewName, &( xHandlers[ 1 ] ) ) == pdFALSE )
            {
                xError = FF_createERR( FF_ERR_NULL_POINTER, FF_MOVE );
                ff_errno = pdFREERTOS_ERRNO_ENXIO; /* No such device or address */
            }
            else if( xHandlers[ 0 ].pxManager != xHandlers[ 1 ].pxManager )
            {
                xError = FF_createERR( FF_ERR_NULL_POINTER, FF_MOVE );
                /* Cross-device link, which can not be done. */
                ff_errno = pdFREERTOS_ERRNO_EXDEV;
            }
            else
            {
                xError = FF_Move( xHandlers[ 0 ].pxManager, xHandlers[ 0 ].pcPath, xHandlers[ 1 ].pcPath, bDeleteIfExists );

                ff_errno = prvFFErrorToErrno( xError );

                #if ffconfigUSE_NOTIFY
                {
                    if( FF_isERR( xError ) == pdFALSE )
                    {
                        callFileEvents( pcNewName, eFileChange );
                    }
                }
                #endif
            }

            #if ( ffconfigHAS_CWD != 0 )
            {
                ffconfigFREE( pcOldCopy );
            }
            #endif
        }
    }

    /* Store the errno to thread local storage. */
    stdioSET_ERRNO( ff_errno );

    if( ff_errno == 0 )
    {
        iReturn = 0;
    }
    else
    {
        /* Return -1 for error as per normal rmdir() semantics. */
        iReturn = -1;
    }

    return iReturn;
}
/*-----------------------------------------------------------*/

int ff_stat( const char * pcName,
             FF_Stat_t * pxStatBuffer )
{
    FF_DirEnt_t xDirEntry;
    uint32_t ulFileCluster;
    FF_Error_t xError;
    int iResult;
    FF_DirHandler_t xHandler;
    BaseType_t xIndex;
    FF_FindParams_t xFindParams;

    #if ( ffconfigUNICODE_UTF16_SUPPORT != 0 )
        const FF_T_WCHAR * pcFileName = NULL;
    #else

        /* Initialised to prevent MSVC incorrectly claiming the variable is used
         * without being initialised. */
        const char * pcFileName = NULL;
    #endif

    memset( &xFindParams, '\0', sizeof( xFindParams ) );

    /* Insert the current working directory in front of relative paths. */
    pcName = prvABSPath( pcName );

    /* Look-up the I/O manager for the file system. */
    if( FF_FS_Find( pcName, &xHandler ) == pdFALSE )
    {
        /* No such device or address. */
        xError = FF_createERR( pdFREERTOS_ERRNO_ENXIO, FF_STAT_FUNC );
    }
    else
    {
        xError = FF_ERR_NONE;
        pcName = xHandler.pcPath;

        /* Let xIndex point to the last occurrence of '/' or '\', to separate
         * the path from the file name. */
        xIndex = ( BaseType_t ) STRLEN( pcName );

        while( xIndex != 0 )
        {
            if( ( pcName[ xIndex ] == '\\' ) || ( pcName[ xIndex ] == '/' ) )
            {
                break;
            }

            xIndex--;
        }

        /* Copy the file name, i.e. the string that comes after the last
         * separator. */
        pcFileName = pcName + xIndex + 1;

        if( xIndex == 0 )
        {
            /* Only for the root, the slash is part of the directory name.
             * 'xIndex' now equals to the length of the path name. */
            xIndex = 1;
        }

        /* FF_CreateShortName() might set flags FIND_FLAG_FITS_SHORT and
         * FIND_FLAG_SIZE_OK. */
        FF_CreateShortName( &xFindParams, pcFileName );

        /* Lookup the path and find the cluster pointing to the directory: */
        xFindParams.ulDirCluster = FF_FindDir( xHandler.pxManager, pcName, ( uint16_t ) xIndex, &xError );
    }

    if( FF_isERR( xError ) == pdFALSE )
    {
        /* See if the file does exist within the given directory. */
        ulFileCluster = FF_FindEntryInDir( xHandler.pxManager, &xFindParams, pcFileName, 0x00, &xDirEntry, &xError );

        if( ulFileCluster == 0ul )
        {
            /* If cluster 0 was returned, it might be because the file has no allocated cluster,
             * i.e. only a directory entry and no stored data. */
            if( STRLEN( pcFileName ) == STRLEN( xDirEntry.pcFileName ) )
            {
                if( ( xDirEntry.ulFileSize == 0 ) && ( FF_strmatch( pcFileName, xDirEntry.pcFileName, ( BaseType_t ) STRLEN( pcFileName ) ) == pdTRUE ) )
                {
                    /* It is the file, give it a pseudo cluster number '1'. */
                    ulFileCluster = 1;
                    /* And reset any error. */
                    xError = FF_ERR_NONE;
                }
            }
        }

        /* Test 'ulFileCluster' again, it might have been changed. */
        if( ulFileCluster == 0ul )
        {
            xError = FF_createERR( FF_ERR_FILE_NOT_FOUND, FF_STAT_FUNC );
        }
    }

    if( ( pxStatBuffer != NULL ) && ( FF_isERR( xError ) == pdFALSE ) )
    {
        if( ( xDirEntry.ucAttrib & FF_FAT_ATTR_DIR ) != 0 )
        {
            pxStatBuffer->st_mode = ( unsigned short ) FF_IFDIR;
        }
        else
        {
            pxStatBuffer->st_mode = ( unsigned short ) FF_IFREG;
        }

        #if ( ffconfigDEV_SUPPORT != 0 )
        {
            BaseType_t bIsDeviceDir = xCheckDevicePath( pcFileName );

            if( bIsDeviceDir != pdFALSE )
            {
                FF_Device_GetDirEnt( xHandler.pcPath, &( xDirEntry ) );
            }
        }
        #endif

        /* Despite the warning output by MSVC - it is not possible to get here
         * if xDirEntry has not been initialised. */
        pxStatBuffer->st_size = xDirEntry.ulFileSize;
        pxStatBuffer->st_ino = xDirEntry.ulObjectCluster;
        pxStatBuffer->st_dev = ( uint16_t ) xHandler.xFSIndex;

        #if ( ffconfigTIME_SUPPORT == 1 )
        {
            pxStatBuffer->ff_atime = prvFileTime( &( xDirEntry.xAccessedTime ) );
            pxStatBuffer->ff_mtime = prvFileTime( &( xDirEntry.xModifiedTime ) );
            pxStatBuffer->ff_ctime = prvFileTime( &( xDirEntry.xCreateTime ) );
        }
        #endif
    }

    stdioSET_ERRNO( prvFFErrorToErrno( xError ) );

    if( FF_isERR( xError ) == pdFALSE )
    {
        iResult = 0;
    }
    else
    {
        iResult = -1;
    }

    return iResult;
} /* ff_stat() */
/*-----------------------------------------------------------*/

#if ( ffconfigHAS_CWD == 1 )

    int ff_chdir( const char * pcDirectoryName )
    {
        int iResult, iLength, iValid = pdFALSE;
        WorkingDirectory_t * pxDir = NULL;

        /* Not all paths set an errno. */
        stdioSET_ERRNO( 0 );

        /* Is there a file system mounted? */
        if( FF_FS_Count() != 0 )
        {
            /* In case a CWD is used, get the absolute path. */
            pcDirectoryName = prvABSPath( pcDirectoryName );
            pxDir = pxFindCWD();

            if( pxDir == NULL )
            {
                /* Store the errno to thread local storage. */
                stdioSET_ERRNO( pdFREERTOS_ERRNO_ENOMEM );

                /* Return -1 for error as per normal chdir() semantics. */
                iResult = -1;
            }
            else
            {
                /* The CWD will be stored without a trailing '/'.  If "/"
                 * happens to be the CWD, it will be stored as an empty string. */
                iLength = ( int ) strlen( pcDirectoryName );

                /* Knock off the trailing / if one exits - being careful not to
                 * remove the trailing slash if this is the root directory. */
                if( ( iLength > 1 ) && ( pxDir->pcFileName[ iLength - 1 ] == '/' ) )
                {
                    pxDir->pcFileName[ iLength - 1 ] = '\0';
                }

                stdioSET_ERRNO( pdFREERTOS_ERRNO_ENOENT );

                /* Does the directory exist? */
                if( strcmp( pcDirectoryName, "/" ) == 0 )
                {
                    /* Moving to the root - which exists. */
                    iValid = pdTRUE;
                }
                else if( ff_finddir( pxDir->pcFileName ) != pdFALSE )
                {
                    iValid = pdTRUE;
                }
            }
        }

        if( iValid == pdTRUE )
        {
            /* The generated name becomes the CWD.  No need to test for overflow
             * as pcPath and pcFileName are the same size. */
            strcpy( pxDir->pcCWD, pxDir->pcFileName );

            /* chdir returns 0 for success. */
            iResult = FF_ERR_NONE;
        }
        else
        {
            /* Return -1 for error as per normal chdir() semantics. */
            iResult = -1;
        }

        return iResult;
    }

#endif /* ffconfigHAS_CWD == 1 */
/*-----------------------------------------------------------*/

#if ( ffconfigHAS_CWD == 1 )

    char * ff_getcwd( char * pcBuffer,
                      size_t xBufferLength )
    {
        WorkingDirectory_t * pxDir = pxFindCWD();

        stdioSET_ERRNO( 0 );

        if( ( pxDir == NULL ) || ( pxDir->pcCWD[ 0 ] == '\0' ) )
        {
            if( xBufferLength > strlen( "/" ) )
            {
                strncpy( pcBuffer, "/", xBufferLength );
            }
            else
            {
                pcBuffer = NULL;
            }
        }
        else
        {
            if( strlen( pxDir->pcCWD ) < xBufferLength )
            {
                strncpy( pcBuffer, pxDir->pcCWD, xBufferLength );
            }
            else
            {
                pcBuffer = NULL;
            }
        }

        return pcBuffer;
    }

#endif /* ffconfigHAS_CWD */
/*-----------------------------------------------------------*/

int ff_findfirst( const char * pcPath,
                  FF_FindData_t * pxFindData )
{
    int iIsRootDir, iReturn;
    const char * pcDirectory;

    iReturn = 0;

    memset( pxFindData, '\0', sizeof( *pxFindData ) );

    pxFindData->pcFileName = pxFindData->xDirectoryEntry.pcFileName;

    /* In case a CWD is used, get the absolute path. */
    pcDirectory = prvABSPath( pcPath );

    if( ( pcDirectory[ 0 ] == '/' ) && ( pcDirectory[ 1 ] == 0x00 ) )
    {
        iIsRootDir = pdTRUE;
    }
    else
    {
        iIsRootDir = pdFALSE;
    }

    /* Find the i/o manager that can handle this path. */
    if( FF_FS_Find( pcDirectory, &( pxFindData->xDirectoryHandler ) ) == pdFALSE )
    {
        if( ( iIsRootDir == pdFALSE ) || ( FF_FS_Count() == 0 ) )
        {
            stdioSET_ERRNO( prvFFErrorToErrno( FF_createERR( FF_ERR_NULL_POINTER, FF_FINDFIRST ) ) );
            iReturn = -1;
        }
    }

    /* Check no errors before continuing. */
    if( iReturn == 0 )
    {
        #if ( ffconfigDEV_SUPPORT != 0 )
        {
            pxFindData->bIsDeviceDir = xCheckDevicePath( pcDirectory );
        }
        #endif

        if( iIsRootDir != pdFALSE )
        {
            /* A listing of the root directory will include pseudo entries
             * such as /ram /nand. */
            pxFindData->xDirectoryHandler.xFSIndex = FF_FS_Count();

            /* Only add '.' */
            pxFindData->xDirectoryHandler.u.bits.bAddDotEntries = stdioDIR_ENTRY_DOT_1;
        }
        else
        {
            /* This is the root of a sub file system, add "." and ".." */
            pxFindData->xDirectoryHandler.u.bits.bAddDotEntries = stdioDIR_ENTRY_DOT_1 | stdioDIR_ENTRY_DOT_2;
        }

        pxFindData->xDirectoryHandler.u.bits.bIsValid = pdTRUE;
        iReturn = ff_findnext( pxFindData );
    }
    else
    {
        /* errno has already been set. */
    }

    return iReturn;
}
/*-----------------------------------------------------------*/

int ff_findnext( FF_FindData_t * pxFindData )
{
    FF_Error_t xError;

    #if ( ffconfigTIME_SUPPORT != 0 )
        BaseType_t xSetTime = 0;
    #endif /* ffconfigTIME_SUPPORT */

    if( pxFindData->xDirectoryHandler.u.bits.bIsValid == pdFALSE )
    {
        xError = FF_createERR( FF_ERR_DIR_INVALID_PARAMETER, FF_FINDNEXT );
        FF_PRINTF( "ff_findnext: xDirectoryHandler not valid\n" );
    }
    else
    {
        xError = FF_createERR( FF_ERR_DIR_END_OF_DIR, FF_FINDNEXT );

        if( pxFindData->xDirectoryHandler.pxManager != NULL )
        {
            if( pxFindData->xDirectoryHandler.u.bits.bFirstCalled == pdFALSE )
            {
                pxFindData->xDirectoryHandler.u.bits.bFirstCalled = pdTRUE;
                xError = FF_FindFirst( pxFindData->xDirectoryHandler.pxManager, &( pxFindData->xDirectoryEntry ),
                                       pxFindData->xDirectoryHandler.pcPath );
            }
            else if( pxFindData->xDirectoryHandler.u.bits.bEndOfDir == pdFALSE )
            {
                xError = FF_FindNext( pxFindData->xDirectoryHandler.pxManager, &( pxFindData->xDirectoryEntry ) );
            }

            if( FF_GETERROR( xError ) == FF_ERR_DIR_END_OF_DIR )
            {
                /* Stop further calls to FF_FindNext(). */
                pxFindData->xDirectoryHandler.u.bits.bEndOfDir = pdTRUE;
            }

            #if ( ffconfigDEV_SUPPORT != 0 )
            {
                if( pxFindData->bIsDeviceDir != pdFALSE )
                {
                    FF_Device_GetDirEnt( pxFindData->xDirectoryHandler.pcPath, &( pxFindData->xDirectoryEntry ) );
                }
            }
            #endif
        }

        if( FF_isERR( xError ) == pdFALSE )
        {
            /* If an entry is found, see if it is a dot-entry.  Dot-entries
             * ("." and "..") need a time-stamp. */
            if( pxFindData->xDirectoryEntry.pcFileName[ 0 ] == '.' )
            {
                if( ( pxFindData->xDirectoryEntry.pcFileName[ 1 ] == '.' ) &&
                    ( pxFindData->xDirectoryEntry.pcFileName[ 2 ] == '\0' ) )
                {
                    /* This is a directory "..". Clear the flag for DOT_2. */
                    pxFindData->xDirectoryHandler.u.bits.bAddDotEntries &= stdioDIR_ENTRY_DOT_1;
                    #if ( ffconfigTIME_SUPPORT != 0 )
                    {
                        /* The dot-entries do not have a proper time stamp, add
                         * it here. */
                        xSetTime = pdTRUE;
                    }
                    #endif /* ffconfigTIME_SUPPORT */
                }
                else if( pxFindData->xDirectoryEntry.pcFileName[ 1 ] == '\0' )
                {
                    /* This is a directory ".". Clear the flag for DOT_1. */
                    pxFindData->xDirectoryHandler.u.bits.bAddDotEntries &= stdioDIR_ENTRY_DOT_2;
                    #if ( ffconfigTIME_SUPPORT != 0 )
                    {
                        xSetTime = pdTRUE;
                    }
                    #endif /* ffconfigTIME_SUPPORT */
                }
            }
        }

        if( FF_GETERROR( xError ) == FF_ERR_DIR_END_OF_DIR )
        {
            /* No more physical entries were found.  Now see if there are FS
             * entries or dot-entries to be added: */
            while( ( pxFindData->xDirectoryHandler.xFSIndex > 0 ) ||
                   ( pxFindData->xDirectoryHandler.u.bits.bAddDotEntries != 0 ) )
            {
                if( pxFindData->xDirectoryHandler.xFSIndex > 0 )
                {
                    FF_SubSystem_t xSubSystem;
                    int found;

                    pxFindData->xDirectoryHandler.xFSIndex--;
                    found = FF_FS_Get( ( int ) pxFindData->xDirectoryHandler.xFSIndex, &xSubSystem );

                    if( ( found == pdFALSE ) || ( xSubSystem.pcPath[ 1 ] == '\0' ) )
                    {
                        continue;
                    }

                    snprintf( pxFindData->xDirectoryEntry.pcFileName, sizeof( pxFindData->xDirectoryEntry.pcFileName ), "%s", xSubSystem.pcPath + 1 );

                    if( xSubSystem.pxManager != NULL )
                    {
                        pxFindData->xDirectoryEntry.ulObjectCluster = xSubSystem.pxManager->xPartition.ulRootDirCluster;
                    }
                    else
                    {
                        pxFindData->xDirectoryEntry.ulObjectCluster = 0;
                    }
                }
                else if( ( pxFindData->xDirectoryHandler.u.bits.bAddDotEntries & stdioDIR_ENTRY_DOT_2 ) != 0 )
                {
                    strcpy( pxFindData->xDirectoryEntry.pcFileName, ".." );

                    /* Clear DOT_2 (keep DOT_1). */
                    pxFindData->xDirectoryHandler.u.bits.bAddDotEntries &= stdioDIR_ENTRY_DOT_1;
                }
                else
                {
                    strcpy( pxFindData->xDirectoryEntry.pcFileName, "." );
                    pxFindData->xDirectoryHandler.u.bits.bAddDotEntries = 0;
                }

                pxFindData->xDirectoryEntry.ucAttrib = FF_FAT_ATTR_READONLY | FF_FAT_ATTR_DIR;
                pxFindData->xDirectoryEntry.ulFileSize = stdioDOT_ENTRY_FILE_SIZE;
                #if ( ffconfigTIME_SUPPORT != 0 )
                {
                    xSetTime = pdTRUE;
                }
                #endif /* ffconfigTIME_SUPPORT */

                xError = FF_ERR_NONE;
                break;
            }
        }

        #if ( ffconfigTIME_SUPPORT != 0 )
        {
            if( xSetTime != pdFALSE )
            {
                FF_TimeStruct_t xTimeStruct;
                time_t xSeconds;

                xSeconds = FreeRTOS_time( NULL );
                FreeRTOS_gmtime_r( &xSeconds, &xTimeStruct );

                pxFindData->xDirectoryEntry.xCreateTime.Year = ( uint16_t ) ( xTimeStruct.tm_year + 1900 ); /* Year (e.g. 2009). */
                pxFindData->xDirectoryEntry.xCreateTime.Month = ( uint16_t ) ( xTimeStruct.tm_mon + 1 );    /* Month (e.g. 1 = Jan, 12 = Dec). */
                pxFindData->xDirectoryEntry.xCreateTime.Day = ( uint16_t ) xTimeStruct.tm_mday;             /* Day (1 - 31). */
                pxFindData->xDirectoryEntry.xCreateTime.Hour = ( uint16_t ) xTimeStruct.tm_hour;            /* Hour (0 - 23). */
                pxFindData->xDirectoryEntry.xCreateTime.Minute = ( uint16_t ) xTimeStruct.tm_min;           /* Min (0 - 59). */
                pxFindData->xDirectoryEntry.xCreateTime.Second = ( uint16_t ) xTimeStruct.tm_sec;           /* Second (0 - 59). */
                /* Date and Time Modified. */
                memcpy( &( pxFindData->xDirectoryEntry.xModifiedTime ),
                        &( pxFindData->xDirectoryEntry.xCreateTime ),
                        sizeof( pxFindData->xDirectoryEntry.xModifiedTime ) );
                /* Date of Last Access. */
                memcpy( &( pxFindData->xDirectoryEntry.xAccessedTime ),
                        &( pxFindData->xDirectoryEntry.xCreateTime ),
                        sizeof( pxFindData->xDirectoryEntry.xAccessedTime ) );
            }
        }
        #endif /* ffconfigTIME_SUPPORT */

        if( FF_GETERROR( xError ) == FF_ERR_DIR_END_OF_DIR )
        {
            /* FF_ERR_DIR_END_OF_DIR will be returned. */
            pxFindData->xDirectoryHandler.u.bits.bIsValid = 0;
        }

        pxFindData->ucAttributes = pxFindData->xDirectoryEntry.ucAttrib;
        pxFindData->ulFileSize = pxFindData->xDirectoryEntry.ulFileSize;
    }

    stdioSET_ERRNO( prvFFErrorToErrno( xError ) );

    return xError;
}
/*-----------------------------------------------------------*/

/*-----------------------------------------------------------
 * ff_isdirempty() returns 1 if a given directory is empty
 * (has no entries)
 *-----------------------------------------------------------*/
int ff_isdirempty( const char * pcPath )
{
    FF_DirHandler_t xHandler;
    int iResult;

    /* In case a CWD is used, get the absolute path */
    pcPath = prvABSPath( pcPath );

    /* Find the i/o manager which can handle this path */
    if( FF_FS_Find( pcPath, &xHandler ) == pdFALSE )
    {
        iResult = ( int ) FF_createERR( FF_ERR_NULL_POINTER, FF_ISDIREMPTY );
    }
    else
    {
        iResult = ( int ) FF_isDirEmpty( xHandler.pxManager, xHandler.pcPath );
    }

    /* Store the errno to thread local storage. */
    stdioSET_ERRNO( prvFFErrorToErrno( iResult ) );
    return iResult;
}
/*-----------------------------------------------------------*/

/* *INDENT-OFF* */
#if ( ffconfig64_NUM_SUPPORT != 0 )
    int64_t ff_diskfree( const char * pcPath,
                         uint32_t * pxSectorCount )
#else
    int32_t ff_diskfree( const char * pcPath,
                         uint32_t * pxSectorCount )
#endif
/* *INDENT-ON* */
{
    FF_DirHandler_t xHandler;
    FF_Error_t xError;

    #if ( ffconfig64_NUM_SUPPORT != 0 )
    #define DISKFREE_RETURN_TYPE    int64_t
        int64_t lReturn;
    #else
    #define DISKFREE_RETURN_TYPE    int32_t
        int32_t lReturn;
    #endif

    if( FF_FS_Find( pcPath, &xHandler ) == pdFALSE )
    {
        /* Return cluster 0 for error. */
        lReturn = 0ul;

        /* Store the errno to thread local storage. */
        stdioSET_ERRNO( pdFREERTOS_ERRNO_ENXIO ); /* No such device or address */
    }
    else
    {
        if( pxSectorCount != NULL )
        {
            *pxSectorCount = xHandler.pxManager->xPartition.ulDataSectors;
        }

        lReturn = ( DISKFREE_RETURN_TYPE ) FF_GetFreeSize( xHandler.pxManager, &xError ) / 512;

        /* Store the errno to thread local storage. */
        stdioSET_ERRNO( prvFFErrorToErrno( xError ) );
    }

    return lReturn;
}
/*-----------------------------------------------------------*/

int ff_finddir( const char * pcPath )
{
    int iResult;
    FF_DirHandler_t xHandler;
    FF_Error_t errCode;

    if( FF_FS_Find( pcPath, &xHandler ) == pdFALSE )
    {
        /* Return cluster 0 for error. */
        iResult = 0;
    }
    else
    {
        iResult = ( int ) FF_FindDir( xHandler.pxManager, xHandler.pcPath, ( uint16_t ) strlen( xHandler.pcPath ), &errCode );
    }

    return iResult;
}
/*-----------------------------------------------------------*/

size_t ff_filelength( FF_FILE * pxStream )
{
    FF_Error_t xReturned;
    uint32_t ulLength;

    xReturned = FF_GetFileSize( pxStream, &( ulLength ) );

    if( FF_isERR( xReturned ) != pdFALSE )
    {
        /* An error. */
        ulLength = ( uint32_t ) 0u;
        stdioSET_ERRNO( prvFFErrorToErrno( xReturned ) );
    }
    else
    {
        stdioSET_ERRNO( pdFREERTOS_ERRNO_NONE );
    }

    return ( size_t ) ulLength;
}
/*-----------------------------------------------------------*/

/*-----------------------------------------------------------
 * Delete a directory and, recursively, all of its contents
 *-----------------------------------------------------------*/
#if ( ffconfigUSE_DELTREE != 0 )
    int ff_deltree( const char * pcDirectory )
    {
        int iResult;
        char * pcPath;

        pcPath = ( char * ) ffconfigMALLOC( ffconfigMAX_FILENAME );

        if( pcPath != NULL )
        {
            /* In case a CWD is used, get the absolute path */
            pcDirectory = prvABSPath( pcDirectory );
            snprintf( pcPath, ffconfigMAX_FILENAME, "%s", pcDirectory );
            /* This recursive function will do all the work */
            iResult = ff_deltree_recurse( pcPath );

            if( iResult >= 0 )
            {
                iResult = ff_rmdir( pcPath );

                if( iResult )
                {
                    FF_PRINTF( "ff_deltree(%s): %s\n", pcPath, strerror( stdioGET_ERRNO() ) );
                }
            }

            ffconfigFREE( pcPath );
        }
        else
        {
            iResult = -1;
            stdioSET_ERRNO( pdFREERTOS_ERRNO_ENOMEM );
        }

        return iResult;
    }
#endif /* ffconfigUSE_DELTREE */
/*-----------------------------------------------------------*/

#if ( ffconfigUSE_DELTREE != 0 )
    static int ff_deltree_recurse( char * pcPath )
    {
        FF_FindData_t * pxFindData;
        BaseType_t xIsDir, xIsDotDir;
        FF_Error_t xError;
        int iResult, iNext, iNameLength, pass, iCount = 0;

        pxFindData = ( FF_FindData_t * ) ffconfigMALLOC( sizeof( *pxFindData ) );

        if( pxFindData != NULL )
        {
            iNameLength = ( int ) strlen( pcPath );

            /* The directory will be scanned 2 times.  First the sub-directories will be
             * entered and their contents deleted.  In the second pass the files in the
             * current directory will be removed.  In this way 'pcPath' can be constantly
             * used and reused recursively which is cheaper than allocating 'ffconfigMAX_FILENAME'
             * bytes within each recursion. */
            for( pass = 0; pass < 2; pass++ )
            {
                for( iResult = ff_findfirst( pcPath, pxFindData );
                     iResult == 0;
                     iResult = iNext )
                {
                    xIsDir = ( pxFindData->xDirectoryEntry.ucAttrib & FF_FAT_ATTR_DIR ) != 0;

                    if( ( pass == 0 ) && ( xIsDir != pdFALSE ) )
                    {
                        /* This entry is a directory.  Don't traverse '.' or '..' */
                        xIsDotDir = 0;

                        if( pxFindData->pcFileName[ 0 ] == '.' )
                        {
                            if( ( pxFindData->pcFileName[ 1 ] == '.' ) &&
                                ( pxFindData->pcFileName[ 2 ] == '\0' ) )
                            {
                                xIsDotDir = 2;
                            }
                            else if( pxFindData->pcFileName[ 1 ] == '\0' )
                            {
                                xIsDotDir = 1;
                            }
                        }

                        if( xIsDotDir == 0 )
                        {
                            snprintf( pcPath + iNameLength, ( size_t ) ( ffconfigMAX_FILENAME - iNameLength ), "%s%s",
                                      pcPath[ iNameLength - 1 ] == '/' ? "" : "/", pxFindData->pcFileName );

                            /* Let pxFindData point to the next element before
                             * the current will get removed. */
                            iNext = ff_findnext( pxFindData );

                            /* Remove the contents of this directory. */
                            iResult = ff_deltree_recurse( pcPath );

                            if( iResult < 0 )
                            {
                                iCount = -1;
                                break;
                            }

                            iCount += iResult;
                            /* remove the directory itself */
                            xError = ff_rmdir( pcPath );

                            if( xError != 0 )
                            {
                                FF_PRINTF( "ff_rmdir( %s ): errno %d\n", pcPath, stdioGET_ERRNO() );
                            }
                            else
                            {
                                iCount++;
                            }
                        }
                        else
                        {
                            iNext = ff_findnext( pxFindData );
                        }
                    }
                    else if( ( pass == 1 ) && ( xIsDir == pdFALSE ) )
                    {
                        snprintf( pcPath + iNameLength, ( size_t ) ( ffconfigMAX_FILENAME - iNameLength ), "%s%s",
                                  pcPath[ iNameLength - 1 ] == '/' ? "" : "/", pxFindData->pcFileName );

                        /* Let pxFindData point to the next element before
                         * the current will get removed. */
                        iNext = ff_findnext( pxFindData );

                        /* Remove a plain file. */
                        xError = ff_remove( pcPath );

                        if( xError != 0 )
                        {
                            FF_PRINTF( "ff_remove( %s ): errno %d\n", pcPath, stdioGET_ERRNO() );
                        }
                        else
                        {
                            iCount++;
                        }
                    }
                    else
                    {
                        iNext = ff_findnext( pxFindData );
                    }

                    pcPath[ iNameLength ] = '\0';
                }

                if( FF_GETERROR( iResult ) == FF_ERR_DIR_INVALID_PATH )
                {
                    break;
                }

                if( ( FF_GETERROR( iResult ) != FF_ERR_DIR_END_OF_DIR ) && ( FF_GETERROR( iResult ) != FF_ERR_FILE_INVALID_PATH ) )
                {
                    FF_PRINTF( "ff_deltree_recurse[%s]: %s\n", pcPath, ( const char * ) FF_GetErrMessage( iResult ) );
                }
            }

            ffconfigFREE( pxFindData );
        }
        else
        {
            iCount = -1;
            stdioSET_ERRNO( pdFREERTOS_ERRNO_ENOMEM );
        }

        return iCount;
    }
#endif /* ffconfigUSE_DELTREE */
/*-----------------------------------------------------------*/

int prvFFErrorToErrno( FF_Error_t xError )
{
    if( FF_isERR( xError ) == pdFALSE )
    {
        return 0;
    }

    /* Store the last +FAT error code received. */
    stdioSET_FF_ERROR( xError );

    switch( FF_GETERROR( xError ) )
    {
        /* Global Error Codes. */
        case FF_ERR_NONE:
            return 0; /* No Error. */

        case FF_ERR_NULL_POINTER:
            return pdFREERTOS_ERRNO_EBADF; /* pxIOManager was NULL. */

        case FF_ERR_NOT_ENOUGH_MEMORY:
            return pdFREERTOS_ERRNO_ENOMEM; /* malloc() failed! - Could not allocate handle memory. */

        case FF_ERR_DEVICE_DRIVER_FAILED:
            return pdFREERTOS_ERRNO_EIO; /* The Block Device driver reported a FATAL error, cannot continue. */

        /* User return codes for Rd/Wr functions:. */
        case FF_ERR_IOMAN_DRIVER_BUSY:
            return pdFREERTOS_ERRNO_EBUSY; /* 10. */

        case FF_ERR_IOMAN_DRIVER_FATAL_ERROR:
            return pdFREERTOS_ERRNO_EUNATCH; /* Protocol driver not attached. */

        /* IOMAN Error Codes. */
        case FF_ERR_IOMAN_BAD_BLKSIZE:
            return pdFREERTOS_ERRNO_EINVAL; /* The provided blocksize was not a multiple of 512. */

        case FF_ERR_IOMAN_BAD_MEMSIZE:
            return pdFREERTOS_ERRNO_EINVAL; /* The memory size was not a multiple of the blocksize. */

        case FF_ERR_IOMAN_DEV_ALREADY_REGD:
            return pdFREERTOS_ERRNO_EADDRINUSE; /* Device was already registered. Use FF_UnRegister() to re-use this IOMAN with another device. */

        case FF_ERR_IOMAN_NO_MOUNTABLE_PARTITION:
            return pdFREERTOS_ERRNO_ENOMEDIUM; /* A mountable partition could not be found on the device. */

        case FF_ERR_IOMAN_INVALID_FORMAT:
            return pdFREERTOS_ERRNO_EFTYPE; /* The. */

        case FF_ERR_IOMAN_INVALID_PARTITION_NUM:
            return pdFREERTOS_ERRNO_EINVAL; /* The partition number provided was out of range. */

        case FF_ERR_IOMAN_NOT_FAT_FORMATTED:
            return pdFREERTOS_ERRNO_EFTYPE; /* The partition did not look like a FAT partition. */

        case FF_ERR_IOMAN_DEV_INVALID_BLKSIZE:
            return pdFREERTOS_ERRNO_EINVAL; /* IOMAN object BlkSize is not compatible with the blocksize of this device driver. */

        case FF_ERR_IOMAN_PARTITION_MOUNTED:
            return pdFREERTOS_ERRNO_EADDRINUSE; /* Device is in use by an actively mounted partition. Unmount the partition first. */

        case FF_ERR_IOMAN_ACTIVE_HANDLES:
            return pdFREERTOS_ERRNO_EBUSY; /* The partition cannot be unmounted until all active file handles are closed. (There may also be active handles on the cache). */

        case FF_ERR_IOMAN_GPT_HEADER_CORRUPT:
            return pdFREERTOS_ERRNO_EBADE; /* The GPT partition table appears to be corrupt, refusing to mount. */

        case FF_ERR_IOMAN_NOT_ENOUGH_FREE_SPACE:
            return pdFREERTOS_ERRNO_ENOSPC; /* 22. */

        case FF_ERR_IOMAN_OUT_OF_BOUNDS_READ:
            return pdFREERTOS_ERRNO_ESPIPE; /* 23, return 'Illegal seek'. */

        case FF_ERR_IOMAN_OUT_OF_BOUNDS_WRITE:
            return pdFREERTOS_ERRNO_ESPIPE; /* 24. */

        case FF_ERR_IOMAN_DRIVER_NOMEDIUM:
            return pdFREERTOS_ERRNO_ENOMEDIUM; /* The medium (e.g. SD-card) has been extracted. */

        /* File Error Codes                         30 +. */
        case FF_ERR_FILE_ALREADY_OPEN:
            return pdFREERTOS_ERRNO_EALREADY; /* File is in use. */

        case FF_ERR_FILE_NOT_FOUND:
            return pdFREERTOS_ERRNO_ENOENT; /* File was not found. */

        case FF_ERR_FILE_OBJECT_IS_A_DIR:
            return pdFREERTOS_ERRNO_EISDIR; /* Tried to FF_Open() a Directory. */

        case FF_ERR_FILE_IS_READ_ONLY:
            return pdFREERTOS_ERRNO_EROFS; /* Tried to FF_Open() a file marked read only. */

        case FF_ERR_FILE_INVALID_PATH:
            return pdFREERTOS_ERRNO_ENOTDIR; /* The path of the file was not found. */

        case FF_ERR_FILE_NOT_OPENED_IN_WRITE_MODE:
            return pdFREERTOS_ERRNO_EACCES; /* 35. */

        case FF_ERR_FILE_NOT_OPENED_IN_READ_MODE:
            return pdFREERTOS_ERRNO_EACCES; /* 36. */

        case FF_ERR_FILE_EXTEND_FAILED:
            return pdFREERTOS_ERRNO_ENOSPC; /* Could not extend the file. */

        case FF_ERR_FILE_DESTINATION_EXISTS:
            return pdFREERTOS_ERRNO_EEXIST; /* 38. */

        case FF_ERR_FILE_SOURCE_NOT_FOUND:
            return pdFREERTOS_ERRNO_ENOENT; /* 39. */

        case FF_ERR_FILE_DIR_NOT_FOUND:
            return pdFREERTOS_ERRNO_ENOENT; /* 40. */

        case FF_ERR_FILE_COULD_NOT_CREATE_DIRENT:
            return pdFREERTOS_ERRNO_EIO; /* 41. */

        case FF_ERR_FILE_BAD_HANDLE:
            return pdFREERTOS_ERRNO_EBADF; /* A file handle was invalid. */

        case FF_ERR_FILE_MEDIA_REMOVED:
            return pdFREERTOS_ERRNO_ENODEV; /* File handle got invalid because media was removed. */

        case FF_ERR_FILE_SEEK_INVALID_POSITION:
            return pdFREERTOS_ERRNO_ESPIPE; /* Illegal position, outside the file's space */

        case FF_ERR_FILE_SEEK_INVALID_ORIGIN:
            return pdFREERTOS_ERRNO_EINVAL; /* Seeking beyond end of file. */

        /* Directory Error Codes                    50 +. */
        case FF_ERR_DIR_OBJECT_EXISTS:
            return pdFREERTOS_ERRNO_EEXIST; /* A file or folder of the same name already exists in the current directory. */

        case FF_ERR_DIR_DIRECTORY_FULL:
            return pdFREERTOS_ERRNO_ENOSPC; /* No more items could be added to the directory. */

        case FF_ERR_DIR_END_OF_DIR:
            return pdFREERTOS_ERRNO_ENMFILE; /*/. */

        case FF_ERR_DIR_NOT_EMPTY:
            return pdFREERTOS_ERRNO_ENOTEMPTY; /* Cannot delete a directory that contains files or folders. */

        case FF_ERR_DIR_INVALID_PATH:
            return pdFREERTOS_ERRNO_EINVAL; /* Could not find the directory specified by the path. */

        case FF_ERR_DIR_CANT_EXTEND_ROOT_DIR:
            return pdFREERTOS_ERRNO_ENOSPC; /* Can't extend the root dir. */

        case FF_ERR_DIR_EXTEND_FAILED:
            return pdFREERTOS_ERRNO_ENOSPC; /* Not enough space to extend the directory. */

        case FF_ERR_DIR_NAME_TOO_LONG:
            return pdFREERTOS_ERRNO_ENAMETOOLONG; /* Name exceeds the number of allowed characters for a filename. */

        /* Fat Error Codes                          70 +. */
        case FF_ERR_FAT_NO_FREE_CLUSTERS:
            return pdFREERTOS_ERRNO_ENOSPC; /* No more free space is available on the disk. */

        /* UNICODE Error Codes                      100 +. */
        case FF_ERR_UNICODE_INVALID_CODE:
            return pdFREERTOS_ERRNO_EBADE; /* An invalid Unicode character was provided!. */

        case FF_ERR_UNICODE_DEST_TOO_SMALL:
            return pdFREERTOS_ERRNO_ENOBUFS; /* Not enough space in the UTF-16 buffer to encode the entire sequence as UTF-16. */

        case FF_ERR_UNICODE_INVALID_SEQUENCE:
            return pdFREERTOS_ERRNO_EILSEQ; /* An invalid UTF-16 sequence was encountered. */

        case FF_ERR_UNICODE_CONVERSION_EXCEEDED:
            return pdFREERTOS_ERRNO_ENAMETOOLONG; /* Filename exceeds MAX long-filename length when converted to UTF-16. */
    }

    return pdFREERTOS_ERRNO_EFAULT;
}
/*-----------------------------------------------------------*/

#if ( ffconfigHAS_CWD == 1 )

    void ff_free_CWD_space( void )
    {
        WorkingDirectory_t * pxSpace;

        /* Obtain the CWD used by the current task. */
        pxSpace = ( WorkingDirectory_t * ) pvTaskGetThreadLocalStoragePointer( NULL, stdioCWD_THREAD_LOCAL_OFFSET );

        if( pxSpace != NULL )
        {
            vTaskSetThreadLocalStoragePointer( NULL, stdioCWD_THREAD_LOCAL_OFFSET, ( void * ) NULL );
            ffconfigFREE( pxSpace );
        }
    }

#endif /* ffconfigHAS_CWD */
/*-----------------------------------------------------------*/

#if ( ffconfigHAS_CWD == 1 )

    static WorkingDirectory_t * pxFindCWD( void )
    {
        WorkingDirectory_t * pxReturn;

        /* Obtain the CWD used by the current task. */
        pxReturn = ( WorkingDirectory_t * ) pvTaskGetThreadLocalStoragePointer( NULL, stdioCWD_THREAD_LOCAL_OFFSET );

        if( pxReturn == NULL )
        {
            /* This task does not yet have a WorkingDirectory_t structure - create and
             * initialise one now. */
            pxReturn = ( WorkingDirectory_t * ) ffconfigMALLOC( sizeof( WorkingDirectory_t ) );

            if( pxReturn != NULL )
            {
                pxReturn->pcCWD[ 0 ] = '\0';
                vTaskSetThreadLocalStoragePointer( NULL, stdioCWD_THREAD_LOCAL_OFFSET, ( void * ) pxReturn );
            }
        }

        return pxReturn;
    }

#endif /* ffconfigHAS_CWD */
/*-----------------------------------------------------------*/

#if ( ffconfigHAS_CWD == 1 )

    static const char * prvProcessRelativePaths( const char * pcPath )
    {
        const char * pcReturn;
        char * pcChar, * pcTokenStart = NULL, * pcFollowingToken, cPreviousChar = 0x00;
        BaseType_t xByte;

        /* Scan the string looking for a relative path. */
        pcReturn = pcPath;
        pcChar = ( char * ) pcReturn;

        configASSERT( pcPath );

        while( *pcChar != 0x00 )
        {
            if( *pcChar == '.' )
            {
                /* A potential relative path was found.  Is this a "." or a "..". */
                if( *( pcChar + 1 ) == '.' )
                {
                    /* Nothing can be done if this is at the start of the string. */
                    if( pcTokenStart != NULL )
                    {
                        /* A ".." was found.  Where does the next token start? */
                        pcFollowingToken = pcChar + 2;

                        if( *pcFollowingToken == '/' )
                        {
                            /* The next token starts after the "../" */
                            pcFollowingToken += sizeof( char );
                        }

                        /* Remove the ".." and the previous token. */
                        xByte = 0;

                        while( pcFollowingToken[ xByte ] != 0x00 )
                        {
                            pcTokenStart[ xByte ] = pcFollowingToken[ xByte ];
                            xByte++;
                        }

                        /* Terminate. */
                        pcTokenStart[ xByte ] = 0x00;

                        /* The pointer to the previous token will now be wrong if
                         * there are multiple if "../.." appears in the string.  So
                         * reset the variables to continue scanning. */
                        pcChar = ( char * ) pcReturn;
                        cPreviousChar = 0x00;
                        pcTokenStart = NULL;
                        continue;
                    }
                }
                else
                {
                    /* A "." was found.  Remove it. */
                }
            }

            if( cPreviousChar == '/' )
            {
                /* This is the start of a new token. */
                pcTokenStart = pcChar;
            }

            cPreviousChar = *pcChar;
            pcChar++;
        }

        /* Make sure there is no / on the end of the string, being careful not to
         * remove the / at the beginning of the string. */
        if( *( pcChar - 1 ) == '/' )
        {
            if( ( pcChar - 1 ) != pcReturn )
            {
                *( pcChar - 1 ) = 0x00;
            }
        }

        return pcReturn;
    }

#endif /* ffconfigHAS_CWD */
/*-----------------------------------------------------------*/

#if ( ffconfigHAS_CWD == 1 )

/*static*/ const char * prvABSPath( const char * pcPath )
    {
        char * pcReturn;
        WorkingDirectory_t * pxWorkingDirectory = pxFindCWD();

        configASSERT( pxWorkingDirectory );

        if( ( pcPath[ 0 ] ) == '/' )
        {
            /* If the path starts with a slash it does not start with a relative
             * path.  Copy the string into a thread local buffer so it can be
             * manipulated without risk of attempting to write to read only
             * memory. */
            snprintf( pxWorkingDirectory->pcFileName, sizeof( pxWorkingDirectory->pcFileName ), "%s", pcPath );
            pcReturn = pxWorkingDirectory->pcFileName;
        }
        else
        {
            /* Insert the working directory into the front of the path. */
            if( pxWorkingDirectory->pcCWD[ 1 ] == 0x00 )
            {
                /* In the root, so don't add a '/' between the CWD and the
                 * file name.
                 * The length parameter also needs a -1 here because we are adding the '/' which would cause
                 * truncation on a full file path length it silences the associated compiler warning.
                 */
                snprintf( pxWorkingDirectory->pcFileName, sizeof( pxWorkingDirectory->pcFileName ) - 1, "/%s", pcPath );
            }
            else
            {
                snprintf( pxWorkingDirectory->pcFileName, sizeof( pxWorkingDirectory->pcFileName ), "%s/%s", pxWorkingDirectory->pcCWD, pcPath );
            }

            pcReturn = pxWorkingDirectory->pcFileName;
        }

        /* Make any adjustments necessitated by relative paths. */
        prvProcessRelativePaths( pcReturn );

        return pcReturn;
    }

#endif /* ffconfigHAS_CWD */

#if ( ffconfigTIME_SUPPORT == 1 )

    time_t prvFileTime( FF_SystemTime_t * pxTime )
    {
        FF_TimeStruct_t xTime;

        xTime.tm_sec = pxTime->Second;
        xTime.tm_min = pxTime->Minute;
        xTime.tm_hour = pxTime->Hour;
        xTime.tm_mday = pxTime->Day;
        xTime.tm_mon = pxTime->Month - 1;
        xTime.tm_year = pxTime->Year - 1900;

        return FreeRTOS_mktime( &xTime );
    }

#endif /* if ( ffconfigTIME_SUPPORT == 1 ) */
/*-----------------------------------------------------------*/
